﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

// ReSharper disable CheckNamespace
namespace Microsoft.Xna.Framework
// ReSharper restore CheckNamespace
{
    public abstract class Game : IDisposable
    {
        private readonly DrawingSurface _drawingSurface;
        private readonly List<IUpdateable> _updateableComponents;
        private readonly List<IDrawable> _drawableComponents;
        private readonly List<IGameComponent> _notYetInitialized = new List<IGameComponent>();
        private readonly GameServiceContainer _gameServices = new GameServiceContainer();
        private readonly GameComponentCollection _gameComponents;
        private readonly LaunchParameters _launchParameters;

        private ContentManager _content;
        private bool _inRun;

        public LaunchParameters LaunchParameters
        {
            get
            {
                return _launchParameters;
            }
        }

        public GameComponentCollection Components
        {
            get
            {
                return _gameComponents;
            }
        }

        public GameServiceContainer Services
        {
            get
            {
                return _gameServices;
            }
        }

        public ContentManager Content
        {
            get
            {
                return _content;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException();
                _content = value;
            }
        }

        public GraphicsDevice GraphicsDevice
        {
            get { return GraphicsDeviceManager.Current.GraphicsDevice; }
        }

        public Game(DrawingSurface drawingSurface)
        {
            _drawingSurface = drawingSurface;
            _updateableComponents = new List<IUpdateable>();
            _drawableComponents = new List<IDrawable>();
            _gameServices = new GameServiceContainer();

            _launchParameters = new LaunchParameters();
            _gameComponents = new GameComponentCollection();
            _gameComponents.ComponentAdded += GameComponentAdded;
            _gameComponents.ComponentRemoved += GameComponentRemoved;
            _content = new ContentManager(_gameServices, "Content");
        }

        ~Game()
        {
            Dispose(false);
        }

        public void Run()
        {
            Initialize();
            BeginRun();
            _inRun = true;
            _drawingSurface.Draw += OnDrawingSurfaceDraw;
        }

        private void OnDrawingSurfaceDraw(object sender, DrawEventArgs e)
        {
            var gameTime = new GameTime(e.TotalTime, e.DeltaTime, false);
            Update(gameTime);
            Draw(gameTime);
            e.InvalidateSurface();
        }

        protected virtual void BeginRun() { }

        protected virtual void EndRun() { }

        protected virtual void Initialize()
        {
            while (_notYetInitialized.Count != 0)
            {
                _notYetInitialized[0].Initialize();
                _notYetInitialized.RemoveAt(0);
            }

            LoadContent();
        }

        protected virtual void Update(GameTime updateGameTime)
        {
            var list = _updateableComponents.Where(uc => uc.Enabled);

            for (int i = 0; i < list.Count(); i++)
            {
                list.ElementAt(i).Update(updateGameTime);
            }
        }

        protected virtual void Draw(GameTime gameTime)
        {
            foreach (IDrawable drawable in _drawableComponents.Where(dc => dc.Visible))
                drawable.Draw(gameTime);
        }

        private void GameComponentRemoved(object sender, GameComponentCollectionEventArgs e)
        {
            if (!_inRun)
                _notYetInitialized.Remove(e.GameComponent);
            var updateable = e.GameComponent as IUpdateable;
            if (updateable != null)
            {
                _updateableComponents.Remove(updateable);
                updateable.UpdateOrderChanged -= UpdateableUpdateOrderChanged;
            }
            var drawable = e.GameComponent as IDrawable;
            if (drawable == null)
                return;
            _drawableComponents.Remove(drawable);
            drawable.DrawOrderChanged -= DrawableDrawOrderChanged;
        }

        private void GameComponentAdded(object sender, GameComponentCollectionEventArgs e)
        {
            if (_inRun)
                e.GameComponent.Initialize();
            else
                _notYetInitialized.Add(e.GameComponent);
            var updateable = e.GameComponent as IUpdateable;
            if (updateable != null)
            {
                int num = _updateableComponents.BinarySearch(updateable, UpdateOrderComparer.Default);
                if (num < 0)
                {
                    int index = ~num;
                    while (index < _updateableComponents.Count && _updateableComponents[index].UpdateOrder == updateable.UpdateOrder)
                        ++index;
                    _updateableComponents.Insert(index, updateable);
                    updateable.UpdateOrderChanged += UpdateableUpdateOrderChanged;
                }
            }
            var drawable = e.GameComponent as IDrawable;
            if (drawable == null)
                return;
            int num1 = _drawableComponents.BinarySearch(drawable, DrawOrderComparer.Default);
            if (num1 >= 0)
                return;
            int index1 = ~num1;
            while (index1 < _drawableComponents.Count && _drawableComponents[index1].DrawOrder == drawable.DrawOrder)
                ++index1;
            _drawableComponents.Insert(index1, drawable);
            drawable.DrawOrderChanged += DrawableDrawOrderChanged;
        }

        private void DrawableDrawOrderChanged(object sender, EventArgs e)
        {
            var drawable = sender as IDrawable;
            _drawableComponents.Remove(drawable);
            int num = _drawableComponents.BinarySearch(drawable, DrawOrderComparer.Default);
            if (num >= 0)
                return;
            int index = ~num;
            while (index < _drawableComponents.Count && _drawableComponents[index].DrawOrder == drawable.DrawOrder)
                ++index;
            _drawableComponents.Insert(index, drawable);
        }

        private void UpdateableUpdateOrderChanged(object sender, EventArgs e)
        {
            var updateable = sender as IUpdateable;
            _updateableComponents.Remove(updateable);
            int num = _updateableComponents.BinarySearch(updateable, UpdateOrderComparer.Default);
            if (num >= 0)
                return;
            int index = ~num;
            while (index < _updateableComponents.Count && _updateableComponents[index].UpdateOrder == updateable.UpdateOrder)
                ++index;
            _updateableComponents.Insert(index, updateable);
        }

        protected virtual void LoadContent() { }

        protected virtual void UnloadContent() { }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposing)
                return;

            lock (this)
            {
                _content.Unload();

                UnloadContent();

                EndRun();

                var components = new IGameComponent[_gameComponents.Count];
                _gameComponents.CopyTo(components, 0);

                foreach (var disposable in components.OfType<IDisposable>())
                {
                    disposable.Dispose();
                }
            }
        }
    }
}
